/*
 *  StanfordPOSTagger.java
 *
 *
 *  Copyright (c) 2007
 *  Knowledge Information & Data Management Laboratory
 *  Sirindhorn International Institute of Technology
 *  Thammasat University
 * 
 *  Arthit Suriyawongkul
 *  art <at> siit.net
 *
 *  art, 15/7/2007
 *
 *  $Id: StanfordPOSTagger.jav 2820 2001-11-14 17:15:43Z oana $
 */

package net.siit.gate;

import java.util.*;
import java.net.URL;

import gate.*;
import gate.creole.*;
import gate.util.*;

import edu.stanford.nlp.ling.Sentence;
import edu.stanford.nlp.ling.TaggedWord;
import edu.stanford.nlp.ling.Word;
import edu.stanford.nlp.tagger.maxent.*;

/**
 * This class is the implementation of the resource DICTIONARYBASEDPOSTAGGER.
 */
public class StanfordPOSTagger  extends AbstractLanguageAnalyser
        implements ProcessingResource {
    
    private MaxentTagger tagger;

    /*
     * this method gets called whenever an object of this
     * class is created either from GATE GUI or if
     * initiated using Factory.createResource() method.
     */
    public Resource init() throws ResourceInstantiationException {                
        try {
            // model filename's extension is ".holder"
            tagger = new MaxentTagger(modelURL.getPath());
            return this;
        } catch (Exception ex) {       
            throw new ResourceInstantiationException("Cannot find model: " + modelURL.getPath());
        }
    }

    /*
     * this method should provide the actual functionality of the PR
     * (from where the main execution begins). This method
     * gets called when user click on the RUN button in the
     * GATE GUIs application window.
     */
    public void execute() throws ExecutionException {
        if (document == null) {
            throw new ExecutionException("No document to process!");
        }

        fireStatusChanged("POS tagging " + document.getName() + "...");

        if (annotationSetName != null && annotationSetName.trim().length() == 0) {
            annotationSetName = null;
        }

        AnnotationSet as = (annotationSetName == null) ?
            document.getAnnotations(null) :
            document.getAnnotations(annotationSetName);
        if (as == null || as.size() == 0) {
            throw new GateRuntimeException("AnnotationSet \"" +
                    annotationSetName + "\" is null or empty.");
        }

        AnnotationSet sentenceAS =
                as.get(ANNIEConstants.SENTENCE_ANNOTATION_TYPE);
        if (sentenceAS == null || sentenceAS.size() == 0) {
            throw new GateRuntimeException(
                    "No Sentences found. Please run sentence splitter first.");
        }

        AnnotationSet tokenAS =
                as.get(ANNIEConstants.TOKEN_ANNOTATION_TYPE);
        if (tokenAS == null || tokenAS.size() == 0) {
            throw new GateRuntimeException(
                    "No Tokens found. Please run tokeniser first.");
        }
        
        /* Do tagging (GATE) sentence by (GATE) sentence.
         * For each (GATE) sentence, construct a (Stanford) sentence from (GATE) tokens,
         * then tag the whole sentence, and add each tag to a feature name "category".
         * 
         * This process made an assumption that, there is no overlapping in Token annotation.
         */
        
        List<Annotation> sentences = new ArrayList<Annotation>(sentenceAS);
        // Does it necessary to sort ???
        //Collections.sort(sentences, new OffsetComparator());
        int sentenceNum = sentences.size();
        int sentenceCount = 0;
        for (Annotation sentence : sentences) {
            Long start = sentence.getStartNode().getOffset();
            Long end = sentence.getEndNode().getOffset();

            List<Annotation> tokens = new ArrayList<Annotation>(tokenAS.get(start, end));
            if (tokens != null && !tokens.isEmpty()) {
                Collections.sort(tokens, new OffsetComparator());

                Sentence ts = new Sentence();
                List<FeatureMap> featureMaps = new ArrayList<FeatureMap>();
                for (Annotation token : tokens) {
                    FeatureMap feat = token.getFeatures();
                    featureMaps.add(feat);
                    
                    String string = (String) feat.get(ANNIEConstants.TOKEN_STRING_FEATURE_NAME);
                    ts.add(new Word(string));
                }
                ts = tagger.processSentence(ts);
                
                /*if (ts.size() != tokens.size()) {
                    fireStatusChanged("ts != tokens");
                }*/

                int i = 0;
                for (FeatureMap feat : featureMaps) {
                    TaggedWord tw = (TaggedWord) ts.get(i++);
                    String tag = tw.tag();
                    feat.put(ANNIEConstants.TOKEN_CATEGORY_FEATURE_NAME, tag);
                }
            }
            fireProgressChanged( (sentenceCount++ / sentenceNum) * 100);
        }

        // dereference
        as = null;

        fireStatusChanged("Part-of-Speech tagging complete!");
        fireProgressChanged(100);
        fireProcessFinished();
    }

    /* this method is called to reinitialize the resource */
    @Override
    public void reInit() throws ResourceInstantiationException {
        this.init();
    }
    
    
   /*
    * There are two types of parameters
    * 1. Init time parameters values for these parameters need to be
    * provided at the time of initializing a new resource and these values are
    * not supposed to be changed.
    * 2. Runtime parameters - values for these parameters are provided at the time
    * of executing the PR. These are runtime parameters and can be
    * changed before starting the execution
    * (i.e. before you click on the "RUN" button in the GATE GUI)
    * It is must to provide setter and getter methods for every such
    * parameter declared in the creole.xml.
    *
    * for example to set a value for outputAnnotationSetName
    */
    private String annotationSetName;
    private URL modelURL;

    // getters, setters
    public String getAnnotationSetName() {
        return annotationSetName;
    }
    public void setAnnotationSetName(String annotationSetName) {
        this.annotationSetName = annotationSetName;
    }

    public URL getModelURL() {
        return modelURL;
    }
    public void setModelURL(URL modelURL) {
        this.modelURL = modelURL;
    }

} // class DictionaryBasedPOSTagger
